package it.avantsoft.ls.gl;

import it.avantsoft.ls.gl.command.AckNewChildCommand;
import it.avantsoft.ls.gl.command.GroupChildCommand;
import it.avantsoft.ls.gl.data.AddressBook;
import it.avantsoft.ls.gl.data.Appraisals;
import org.apache.log4j.Logger;

import java.io.IOException;
import java.net.InetAddress;
import java.net.Socket;
import java.net.UnknownHostException;
import java.util.Collection;
import java.util.TimerTask;
import java.util.concurrent.LinkedBlockingQueue;

public class GroupChildHandler extends Thread implements GroupChildEventListener {
    private static Logger log = Logger.getLogger(GroupChildHandler.class);

    private boolean stop;

    private GroupLeaderHandler groupLeaderHandler;

    private String childId;
    private Socket socket;
    private String host;
    private int port;
    private int listeningPort;

    private SenderMessageToChild senderMessage;
    private LinkedBlockingQueue<GroupChildCommand> commandQueue;
    private ReceiverCommandFromChild receiverCommand;

    private String addingChildId;

    public GroupChildHandler(GroupLeaderHandler groupLeaderHandler, Socket childSocket, String childId) {
        super("GroupChildHandler " + groupLeaderHandler.getNumber() + ' ' + childId);
        this.groupLeaderHandler = groupLeaderHandler;
        this.childId = childId;
        this.socket = childSocket;
        this.port = childSocket.getPort();
        try {
            this.host = InetAddress.getLocalHost().getHostAddress();
        } catch (UnknownHostException e) {
            log.error("get host address", e);
        }
        stop = false;
    }

    public void init() {
        try {
            senderMessage = new SenderMessageToChild(this, socket.getOutputStream());

            commandQueue = new LinkedBlockingQueue<GroupChildCommand>();

            receiverCommand = new ReceiverCommandFromChild(this, socket.getInputStream(), commandQueue);
            receiverCommand.start();

            start();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public String getChildId() {
        return childId;
    }

    public void setChildId(String childId) {
        setName("GroupChildHandler " + groupLeaderHandler.getNumber() + " " + childId);
        receiverCommand.setName("ReceiverCommandFromChild " + childId);
        this.childId = childId;
    }

    public String getHost() {
        return host;
    }

    public void setListeningPort(int listeningPort) {
        this.listeningPort = listeningPort;
    }

    public int getListeningPort() {
        return listeningPort;
    }

    public boolean isConnected() {
        return socket.isInputShutdown();
    }

    public void shutdown() {
        try {
            socket.close();
        } catch (IOException e) {

        } finally {
            stop = true;
            log.info("socket closed");
        }
    }

    public void run() {
        while (!stop) {
            GroupChildCommand groupChildCommand = getCommadFromQueue();
            log.info("receive " + groupChildCommand);

            groupChildCommand.execute();
        }
        System.out.println(socket + " : " + socket.isConnected());
        log.info("stopped");
    }

    private GroupChildCommand getCommadFromQueue() {
        GroupChildCommand groupChildCommand = null;
        try {
            groupChildCommand = commandQueue.take();
        } catch (InterruptedException e) {
            log.error("get command from queue", e);
        }
        return groupChildCommand;
    }

    public String toString() {
        return "GroupChildHandler{" +
                "childId='" + childId + '\'' +
                ", stop=" + stop +
                ", host='" + host + '\'' +
                ", listeningPort=" + listeningPort +
                '}';
    }

    ///////////////////////////////////////////////////////////

    public boolean welcome() {
        return groupLeaderHandler.welcome(this);
    }

    public boolean connect(String oldChildId) {
        return groupLeaderHandler.connect(this, oldChildId);
    }

    public boolean ackNewChild(String newChildId, String groupLeaderHost, int groupLeaderListeningPort) {
        boolean success = false;
        if (addingChildId != null && addingChildId.equals(newChildId)) {
            addingChildId = null;
            success = groupLeaderHandler.ackNewChild(newChildId, groupLeaderHost, groupLeaderListeningPort);
        } else {
            log.info("AckNewChildCommand ERROR: New Child unknown -> " + newChildId + " expecting child -> " + addingChildId);
        }
        return success;
    }

    public boolean reconnect(String exGroupLeaderId, String peerId) {
        return groupLeaderHandler.reconnect(this, exGroupLeaderId, peerId);
    }

    public boolean newAppraisals(Appraisals appraisals) {
        return groupLeaderHandler.newAppraisals(appraisals);
    }

    ///////////////////// Send Messages

    public boolean sendAB(AddressBook ab) {
        return senderMessage.AB(groupLeaderHandler.getPeerId(), ab);
    }

    public boolean sendAB(AddressBook lowerAB, AddressBook centerAB) {

        centerAB.add(groupLeaderHandler.getSelfInfo().getKey(), groupLeaderHandler.getSelfInfo().getValue());
        boolean sent = senderMessage.AB(groupLeaderHandler.getPeerId(), lowerAB, centerAB);
        centerAB.remove(groupLeaderHandler.getSelfInfo().getKey());

        return sent;
    }

    //...per adesso si può chiedere un aggiunta di un child alla volta
    public boolean sendNewChild(String newChildId, boolean schedule) {
        boolean sent = false;
        if (addingChildId == null) {
            addingChildId = newChildId;

            TimerTask timerCommand = null;
            if (schedule) {
                //scheduliamo un comando che tornerà entro un timeout
                //il comando contiene condizioni di fallimento
                AckNewChildCommand ackNewChildCommand = new AckNewChildCommand(this, newChildId, null, 0);
                timerCommand = receiverCommand.scheduleTimerCommand(childId, ackNewChildCommand);
                log.info("scheduled " + ackNewChildCommand);
            }

            sent = senderMessage.newChild(newChildId, port);
            if (!sent) {
                if (schedule) {
                    log.error("msg not sent: " + timerCommand + ' ' + ((timerCommand.cancel()) ? "cancelled" : "not cancelled"));
                }
            }
        }
        return sent;
    }

    public boolean sendConnectToGL(String groupLeaderHost, int groupLeaderPort) {
        return senderMessage.connectToGL(getChildId(), groupLeaderHost, groupLeaderPort);
    }

    public boolean sendPong() {
        return senderMessage.pong();
    }

    public boolean sendHorizontalBalance(int loadDifference, GroupChildHandler lessLoaded) {
        return senderMessage.horizontalBalance(loadDifference, lessLoaded);
    }

    public boolean signalBrother(String id, int port) {
        return senderMessage.signalBrother(id, port);
    }

    public boolean signalUncle(String id, int port) {
        return senderMessage.signalUncle(id, port);
    }

    public boolean signalNiece(String id) {
        return senderMessage.signalNiece(id);
    }

    public GroupChildHandler getChildHandler(String peerHost) {
        return groupLeaderHandler.getChildHandler(peerHost);
    }

    public Collection<GroupChildHandler> getLabHandlers() {
        return groupLeaderHandler.getLabHandlers();
    }

    public Collection<GroupChildHandler> getLabHandlers(String excludePeer) {
        return groupLeaderHandler.getLabHandlers(excludePeer);
    }

    public boolean signalSon(String childId) {
        return groupLeaderHandler.signalSonToGL(childId);
    }

    public boolean signalDeadNiece(String id) {
        return senderMessage.signalDeadNiece(id);
    }

    public boolean signalDeadUncle(String peerID) {
        return senderMessage.signalDeadUncle(peerID);
    }

    public boolean signalDeadSonEvent(String childId) {
        return groupLeaderHandler.signalDeadSon(childId);
    }

    public boolean signalDeadSon(String childId) {
        return senderMessage.signalDeadBrother(childId);
    }

    public boolean peerKnown(String childId) {
        return groupLeaderHandler.peerKnown(childId);
    }

    public boolean isAllowed(String peerId) {
        return groupLeaderHandler.isAllowed(peerId);
    }

    public boolean sendAckHorizontalBalance(String incomingPeer, String designedGroupLeaderHost, int designedGroupLeaderListeningPort) {
        return senderMessage.sendAckHorizontalBalance(incomingPeer, designedGroupLeaderHost, designedGroupLeaderListeningPort);
    }
}
